/* global define CodeMirror Image */
// This plugin renders markdown block images

(function (mod) {
  if (typeof exports === 'object' && typeof module === 'object') { // CommonJS
    mod(require('codemirror/lib/codemirror'))
  } else if (typeof define === 'function' && define.amd) { // AMD
    define(['codemirror/lib/codemirror'], mod)
  } else { // Plain browser env
    mod(CodeMirror)
  }
})(function (CodeMirror) {
  'use strict'

  // GENERAL PLUGIN VARIABLES
  const { getImageRE } = require('../../../common/regular-expressions')
  const makeAbsoluteCachefreeURL = require('../../../common/util/make-absolute-cachefree-url')

  // Image detection regex
  var imageRE = getImageRE()

  // Holds the currently rendered images
  var imageMarkers = []
  var currentDocID = null

  // This variable holds a base64 encoded placeholder image.
  var img404 = ''

  /**
   * Defines the CodeMirror command to render all found markdown images.
   * @param  {CodeMirror} cm The calling CodeMirror instance
   * @return {void}    Commands do not return.
   */
  CodeMirror.commands.markdownRenderImages = function (cm) {
    let i = 0

    if (currentDocID !== cm.doc.id) {
      currentDocID = cm.doc.id
      for (let marker of imageMarkers) {
        if (marker.find()) marker.clear()
      }
      imageMarkers = [] // Flush it away!
    }

    // First remove images that may not exist anymore. As soon as someone
    // clicks into the image, it will be automatically removed, as well as
    // if someone simply deletes the whole line.
    do {
      if (!imageMarkers[i]) continue

      if (imageMarkers[i] && imageMarkers[i].find() === undefined) {
        // Marker is no longer present, so splice it
        imageMarkers.splice(i, 1)
      } else {
        // Array is same size, so increase i
        i++
      }
    } while (i < imageMarkers.length)

    // Now render all potential new images
    for (let i = 0; i < cm.lineCount(); i++) {
      if (cm.getModeAt({ 'line': i, 'ch': 0 }).name !== 'markdown') continue

      // Always reset lastIndex property, because test()-ing on regular
      // expressions advance it.
      imageRE.lastIndex = 0

      // First get the line and test if the contents contain an image
      let line = cm.getLine(i)
      if (!imageRE.test(line)) continue

      imageRE.lastIndex = 0 // Necessary because of global flag in RegExp

      let match

      // Run through all links on this line
      while ((match = imageRE.exec(line)) != null) {
        let altText = match[1] || ''
        let title = match[3] || altText
        let url = match[2] || ''

        // Now get the precise beginning of the match and its end
        let curFrom = { 'line': i, 'ch': match.index }
        let curTo = { 'line': i, 'ch': match.index + match[0].length }

        let cur = cm.getCursor('from')
        if (cur.line === curFrom.line && cur.ch >= curFrom.ch && cur.ch <= curTo.ch + 1) {
          // Cursor is in selection: Do not render.
          continue
        }

        // Has this thing already been rendered?
        let con = false
        let marks = cm.findMarks(curFrom, curTo)
        for (let marx of marks) {
          if (imageMarkers.includes(marx)) {
            // We've got communism. (Sorry for the REALLY bad pun.)
            con = true
            break
          }
        }
        if (con) continue // Skip this match

        // Do not render if it's inside a comment (in this case the mode will be
        // markdown, but comments shouldn't be included in rendering)
        // Final check to avoid it for as long as possible, as getTokenAt takes
        // considerable time.
        let tokenTypeBegin = cm.getTokenTypeAt(curFrom)
        let tokenTypeEnd = cm.getTokenTypeAt(curTo)
        if ((tokenTypeBegin && tokenTypeBegin.includes('comment')) ||
        (tokenTypeEnd && tokenTypeEnd.includes('comment'))) {
          continue
        }

        let img = new Image()
        // Now add a line widget to this line.
        let textMarker = cm.markText(
          curFrom,
          curTo,
          {
            'clearOnEnter': true,
            'replacedWith': img,
            'handleMouseEvents': true
          }
        )

        // Retrieve the size constraints
        let width = (cm.getOption('imagePreviewWidth')) ? cm.getOption('imagePreviewWidth') + '%' : '100%'
        let height = (cm.getOption('imagePreviewHeight') && cm.getOption('imagePreviewHeight') < 100) ? cm.getOption('imagePreviewHeight') + 'vh' : ''
        img.style.maxWidth = width
        img.style.maxHeight = height
        img.style.cursor = 'default' // Nicer cursor
        img.alt = altText
        // Display a replacement image in case the correct one is not found
        img.onerror = (e) => { img.src = img404 }
        img.onclick = (e) => { textMarker.clear() }

        // Update the image title on load to retrieve the real image size.
        img.onload = () => {
          img.title = `${title} (${img.naturalWidth}x${img.naturalHeight}px)`
          textMarker.changed()
        }

        // Check if there is a title to replace
        if (match[3]) url = url.replace(`"${match[3]}"`, '').trim()

        // Finally set the src to begin the loading process of the image
        if (/data:[a-zA-Z0-9/;=]+(?:;base64){0,1},.+/.test(url)) {
          img.src = url
        } else {
          img.src = makeAbsoluteCachefreeURL(cm.getOption('markdownImageBasePath'), url)
        }

        // Push the textMarker into the array
        imageMarkers.push(textMarker)
      }
    }
  }
})
